﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

/*
 * I LoadContent
 * Debug.Shape.LoadContent(GraphicsDevice, Content);
 * 
 * Och långt ner i draw innan base.Draw
 * Debug.Shape.RenderDebugData
 * 
 * en sak som är lurig med den är att den använder sig av matriser som kameraklass
 * men det går att inaktivera
 * genom att skicka in en identitetsmatris
 * Matrix m = Matrix.Identity;
 * Debug.Shape.RenderDebugData(ref m);
 * 
 *  Debug.Shape.DrawLine(10, 10, 100, 100, Color.Red);
 * Och
 * till varje finns en Local-version
 * den bortser från kameran
 * och om du använder nåt eget

 * */
namespace Fattyclimb
{
    class Debug
    {
        private static DebugShapeRenderer _shape = null;
        public static DebugShapeRenderer Shape
        {
            get
            {
                if (_shape == null)
                    _shape = new DebugShapeRenderer();
                return _shape;
            }
        }
    }

    class DebugShapeRenderer
    {
        #region Member variables
        VertexPositionColor[] _localVertsLines;
        VertexPositionColor[] _localVertsFill;
        int _localLineCount;
        int _localFillCount;

        VertexPositionColor[] _vertsLines;
        VertexPositionColor[] _vertsFill;
        int _lineCount;
        int _fillCount;

        SpriteBatch _batch;
        SpriteFont _font;
        GraphicsDevice _device;

        int _maxPrimitiveCount;
        List<StringData> _localStringData;
        List<StringData> _stringData;

        BasicEffect _effect;
        BasicEffect _localEffect;

        public const int CircleSegments = 16;
        private Vector2[] CircleData = new Vector2[CircleSegments + 1];
        #endregion

        public DebugShapeRenderer()
        {
            for (int i = 0; i < CircleData.Length; ++i)
            {
                CircleData[i].X = (float)Math.Cos((double)i * Math.PI * 2.0 / (double)CircleSegments);
                CircleData[i].Y = (float)Math.Sin((double)i * Math.PI * 2.0 / (double)CircleSegments);
            }
        }

        public void LoadContent(GraphicsDevice device, ContentManager content)
        {
            _batch = new SpriteBatch(device);
            _font = content.Load<SpriteFont>("font");
            _device = device;

            _effect = new BasicEffect(device);
            _effect.VertexColorEnabled = true;

            _localEffect = new BasicEffect(device);
            _localEffect.VertexColorEnabled = true;

            _stringData = new List<StringData>();
            _localStringData = new List<StringData>();

            _maxPrimitiveCount = device.GraphicsProfile == GraphicsProfile.Reach ? 65535 : 1048575;
            _vertsLines = new VertexPositionColor[_maxPrimitiveCount];
            _vertsFill = new VertexPositionColor[_maxPrimitiveCount];

            _localVertsLines = new VertexPositionColor[_maxPrimitiveCount];
            _localVertsFill = new VertexPositionColor[_maxPrimitiveCount];
        }

        #region DrawPoint
        public void DrawPoint(float x, float y, float size, Color color)
        {
            Vector2[] verts = new Vector2[4];
            float hs = size / 2.0f;
            verts[0].X = x - hs;
            verts[0].Y = x - hs;
            verts[1].X = x + hs;
            verts[1].Y = x - hs;
            verts[2].X = x + hs;
            verts[2].Y = x + hs;
            verts[3].X = x - hs;
            verts[3].Y = x + hs;

            Color fill = new Color(color.ToVector3() / 2.0f);
            DrawSolidPolygon(verts, 4, fill, color);
        }

        public void DrawPointLocal(float x, float y, float size, Color color)
        {
            Vector2[] verts = new Vector2[4];
            float hs = size / 2.0f;
            verts[0].X = x - hs;
            verts[0].Y = x - hs;
            verts[1].X = x + hs;
            verts[1].Y = x - hs;
            verts[2].X = x + hs;
            verts[2].Y = x + hs;
            verts[3].X = x - hs;
            verts[3].Y = x + hs;

            Color fill = new Color(color.ToVector3() / 2.0f);
            DrawSolidPolygonLocal(verts, 4, fill, color);
        }
        #endregion

        #region DrawLine
        public void DrawLine(float x1, float y1, float x2, float y2, Color color)
        {
            _vertsLines[_lineCount * 2].Position.X = x1;
            _vertsLines[_lineCount * 2].Position.Y = y1;
            _vertsLines[_lineCount * 2].Color = color;
            _vertsLines[_lineCount * 2 + 1].Position.X = x2;
            _vertsLines[_lineCount * 2 + 1].Position.Y = y2;
            _vertsLines[_lineCount * 2 + 1].Color = color;
            ++_lineCount;
        }
        public void DrawLineLocal(float x1, float y1, float x2, float y2, Color color)
        {
            _localVertsLines[_localLineCount * 2].Position.X = x1;
            _localVertsLines[_localLineCount * 2].Position.Y = y1;
            _localVertsLines[_localLineCount * 2].Color = color;
            _localVertsLines[_localLineCount * 2 + 1].Position.X = x2;
            _localVertsLines[_localLineCount * 2 + 1].Position.Y = y2;
            _localVertsLines[_localLineCount * 2 + 1].Color = color;
            ++_localLineCount;
        }
        #endregion

        #region DrawCircle
        public void DrawCircle(float x, float y, float r, Color color)
        {
            DrawCircle(ref _vertsLines, ref _lineCount, x, y, r, color);
        }

        public void DrawCircleLocal(float x, float y, float r, Color color)
        {
            DrawCircle(ref _localVertsLines, ref _localLineCount, x, y, r, color);
        }

        public void DrawSolidCircle(float x, float y, float r, Color color)
        {
            DrawCircle(ref _vertsFill, ref _fillCount, x, y, r, color);
        }

        public void DrawSolidCircleLocal(float x, float y, float r, Color color)
        {
            DrawCircle(ref _localVertsFill, ref _localFillCount, x, y, r, color);
        }

        public void DrawSolidCircle(float x, float y, float r, Color color, Color outline)
        {
            DrawSolidCircle(ref _vertsFill, ref _fillCount, x, y, r, color);
            DrawCircle(ref _vertsLines, ref _lineCount, x, y, r, outline);
        }

        public void DrawSolidCircleLocal(float x, float y, float r, Color color, Color outline)
        {
            DrawCircle(ref _localVertsFill, ref _localFillCount, x, y, r, color);
            DrawCircle(ref _localVertsLines, ref _localLineCount, x, y, r, outline);
        }

        private void DrawCircle(ref VertexPositionColor[] internalVerts, ref int internalCount, float x, float y, float r, Color color)
        {
            for (int i = 0; i < CircleSegments; ++i)
            {
                internalVerts[internalCount * 2].Color = color;
                internalVerts[internalCount * 2].Position.X = CircleData[i].X * r + x;
                internalVerts[internalCount * 2].Position.Y = CircleData[i].Y * r + y;

                internalVerts[internalCount * 2 + 1].Color = color;
                internalVerts[internalCount * 2 + 1].Position.X = CircleData[i + 1].X * r + x;
                internalVerts[internalCount * 2 + 1].Position.Y = CircleData[i + 1].Y * r + y;

                internalCount++;
            }
        }

        private void DrawSolidCircle(ref VertexPositionColor[] internalVerts, ref int internalCount, float x, float y, float r, Color color)
        {
            for (int i = 0; i < CircleSegments; ++i)
            {
                internalVerts[internalCount * 3].Color = color;
                internalVerts[internalCount * 3].Position.X = x;
                internalVerts[internalCount * 3].Position.Y = y;

                internalVerts[internalCount * 3 + 1].Color = color;
                internalVerts[internalCount * 3 + 1].Position.X = CircleData[i].X * r + x;
                internalVerts[internalCount * 3 + 1].Position.Y = CircleData[i].Y * r + y;

                internalVerts[internalCount * 3 + 2].Color = color;
                internalVerts[internalCount * 3 + 2].Position.X = CircleData[i + 1].X * r + x;
                internalVerts[internalCount * 3 + 2].Position.Y = CircleData[i + 1].Y * r + y;

                internalCount++;
            }
        }
        #endregion

        #region DrawPolygon
        public void DrawPolygon(Vector2[] vertices, int count, Color color)
        {
            DrawPolygon(ref _vertsLines, ref _lineCount, vertices, count, color);
        }

        public void DrawPolygonLocal(Vector2[] vertices, int count, Color color)
        {
            DrawPolygon(ref _localVertsLines, ref _localLineCount, vertices, count, color);
        }

        private void DrawPolygon(ref VertexPositionColor[] internalVerts, ref int internalCount, Vector2[] vertices, int count, Color color)
        {
            for (int i = 0; i < count - 1; i++)
            {
                internalVerts[internalCount * 2].Position = new Vector3(vertices[i], -0.1f);
                internalVerts[internalCount * 2].Color = color;
                internalVerts[internalCount * 2 + 1].Position = new Vector3(vertices[i + 1], -0.1f);
                internalVerts[internalCount * 2 + 1].Color = color;
                internalCount++;
            }

            internalVerts[internalCount * 2].Position = new Vector3(vertices[count - 1], -0.1f);
            internalVerts[internalCount * 2].Color = color;
            internalVerts[internalCount * 2 + 1].Position = new Vector3(vertices[0], -0.1f);
            internalVerts[internalCount * 2 + 1].Color = color;
            internalCount++;
        }
        #endregion

        #region DrawSolidPolygon
        public void DrawSolidPolygon(Vector2[] vertices, int count, Color color)
        {
            DrawSolidPolygon(ref _vertsFill, ref _fillCount, vertices, count, color);
        }

        public void DrawSolidPolygon(Vector2[] vertices, int count, Color color, Color outline)
        {
            DrawSolidPolygon(vertices, count, color);
            DrawPolygon(vertices, count, outline);
        }

        public void DrawSolidPolygonLocal(Vector2[] vertices, int count, Color color)
        {
            DrawSolidPolygon(ref _localVertsFill, ref _localFillCount, vertices, count, color);
        }

        public void DrawSolidPolygonLocal(Vector2[] vertices, int count, Color color, Color outline)
        {
            DrawSolidPolygon(ref _localVertsFill, ref _localFillCount, vertices, count, color);
            DrawPolygonLocal(vertices, count, outline);
        }

        public void DrawSolidPolygon(ref VertexPositionColor[] internalVerts, ref int internalCount, Vector2[] vertices, int count, Color color)
        {
            if (count == 2)
            {
                DrawPolygon(vertices, count, color);
                return;
            }

            for (int i = 1; i < count - 1; i++)
            {
                internalVerts[internalCount * 3].Position = new Vector3(vertices[0], -0.1f);
                internalVerts[internalCount * 3].Color = color;

                internalVerts[internalCount * 3 + 1].Position = new Vector3(vertices[i], -0.1f);
                internalVerts[internalCount * 3 + 1].Color = color;

                internalVerts[internalCount * 3 + 2].Position = new Vector3(vertices[i + 1], -0.1f);
                internalVerts[internalCount * 3 + 2].Color = color;

                internalCount++;
            }
        }
        #endregion

        #region DrawString
        public void DrawString(float x, float y, Color color, StringOrigin position, string s, params object[] args)
        {
            _stringData.Add(new StringData(x, y, s, args, color, position));
        }

        public void DrawStringLocal(float x, float y, Color color, StringOrigin position, string s, params object[] args)
        {
            _localStringData.Add(new StringData(x, y, s, args, color, position));
        }

        public void DrawString(float x, float y, Color color, string s, params object[] args)
        {
            _stringData.Add(new StringData(x, y, s, args, color, StringOrigin.TopLeft));
        }

        public void DrawStringLocal(float x, float y, Color color, string s, params object[] args)
        {
            _localStringData.Add(new StringData(x, y, s, args, color, StringOrigin.TopLeft));
        }

        #endregion

        #region RenderDebugData
        public void RenderDebugData(ref Matrix projection, ref Matrix view)
        {
            _effect.View = view;
            RenderDebugData(ref projection);
        }

        public void RenderDebugData(ref Matrix projection)
        {
            _device.RasterizerState = RasterizerState.CullNone;
            _device.DepthStencilState = DepthStencilState.Default;

            _localEffect.Projection = Matrix.CreateOrthographicOffCenter(0f, _device.Viewport.Width,
                _device.Viewport.Height, 0f, 0f, 1f);

            //Graph stuff first
            _localEffect.Techniques[0].Passes[0].Apply();

            if (_localFillCount > 0)
                _device.DrawUserPrimitives(PrimitiveType.TriangleList, _localVertsFill, 0, _localFillCount);

            if (_localLineCount > 0)
                _device.DrawUserPrimitives(PrimitiveType.LineList, _localVertsLines, 0, _localLineCount);

            // set the effects projection matrix
            _effect.Projection = projection;

            // we should have only 1 technique and 1 pass
            _effect.Techniques[0].Passes[0].Apply();

            // make sure we have stuff to draw
            if (_fillCount > 0)
                _device.DrawUserPrimitives(PrimitiveType.TriangleList, _vertsFill, 0, _fillCount);

            // make sure we have lines to draw
            if (_lineCount > 0)
                _device.DrawUserPrimitives(PrimitiveType.LineList, _vertsLines, 0, _lineCount);

            // begin the sprite batch effect
            _batch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);

            Matrix vp = Matrix.Multiply(_effect.View, projection); ;
            // draw any strings we have
            for (int i = 0; i < _stringData.Count; i++)
            {
                Vector2 pos = new Vector2(_stringData[i].X, _stringData[i].Y);
                pos = Vector2.Transform(pos, vp);
                pos.X = (pos.X + 1f) * _device.DisplayMode.Height * 0.5f;
                pos.Y = (-pos.Y + 1f) * _device.DisplayMode.Width * 0.5f;

                string S = string.Format(_stringData[i].S, _stringData[i].Args);
                Vector2 size = _font.MeasureString(S);
                Vector2 origin = new Vector2();
                int flags = (int)_stringData[i].Origin;

                if ((flags & (int)StringOrigin.Right) != 0)
                    origin.X += size.X;
                else if ((flags & (int)StringOrigin.Left) == 0)
                    origin.X += size.X * 0.5f;

                if ((flags & (int)StringOrigin.Bottom) != 0)
                    origin.Y += size.Y;
                else if ((flags & (int)StringOrigin.Top) == 0)
                    origin.Y += size.Y * 0.5f;

                origin.X = (int)origin.X;//Avoid aliasing.
                origin.Y = (int)origin.Y;
                pos.X = (int)pos.X;
                pos.Y = (int)pos.Y;
                _batch.DrawString(_font, S, pos, _stringData[i].Color, 0.0f, origin, 1.0f, SpriteEffects.None, 0);
            }

            for (int i = 0; i < _localStringData.Count; i++)
            {
                string S = string.Format(_localStringData[i].S, _localStringData[i].Args);
                Vector2 size = _font.MeasureString(S);
                Vector2 origin = new Vector2();
                int flags = (int)_localStringData[i].Origin;

                if ((flags & (int)StringOrigin.Right) != 0)
                    origin.X += size.X;
                else if ((flags & (int)StringOrigin.Left) == 0)
                    origin.X += size.X * 0.5f;

                if ((flags & (int)StringOrigin.Bottom) != 0)
                    origin.Y += size.Y;
                else if ((flags & (int)StringOrigin.Top) == 0)
                    origin.Y += size.Y * 0.5f;

                Vector2 pos = new Vector2(_localStringData[i].X, _localStringData[i].Y);

                origin.X = (int)origin.X;//Avoid aliasing.
                origin.Y = (int)origin.Y;
                pos.X = (int)pos.X;
                pos.Y = (int)pos.Y;
                _batch.DrawString(_font, S, pos, _localStringData[i].Color, 0.0f, origin, 1.0f, SpriteEffects.None, 0);

            }
            // end the sprite batch effect
            _batch.End();

            _stringData.Clear();
            _localStringData.Clear();
            _lineCount = _fillCount = _localLineCount = _localFillCount = 0;
        }
        #endregion

        #region Internal class: StringData
        public enum StringOrigin
        {
            //Dir       Bit
            //Left      1
            //Right     2
            //Top       4
            //Bottom    8
            TopLeft = 1 | 4,
            TopRight = 2 | 4,
            BottomLeft = 8 | 1,
            BottomRight = 8 | 2,
            Center = 0,
            Top = 4,
            Bottom = 8,
            Right = 2,
            Left = 1
        }

        private struct StringData
        {
            public StringOrigin Origin;
            public object[] Args;
            public Color Color;
            public string S;
            public float X, Y;

            public StringData(float x, float y, string s, object[] args, Color color, StringOrigin origin)
            {
                X = x;
                Y = y;
                S = s;
                Args = args;
                Color = color;
                Origin = origin;
            }
        }
        #endregion
    }


}
